package java_enum;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
    private final Map<Object, T> nameToConstant = new HashMap<>();
    private final Map<T, Object> constantToName = new HashMap<>();

    public EnumTypeAdapter(Class<T> classOfT) {
        try {
            for (T constant : classOfT.getEnumConstants()) {
                String name = constant.name();
                SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
                if (annotation != null) {
                    name = annotation.value();
                    for (String alternate : annotation.alternate()) {
                        nameToConstant.put(alternate, constant);
                    }
                }
                // 直接调用通用方法 getValue (注意：默认所有生成的 enum 中都有此方法)
                Object value = null;
                try {
                    value = constant.getClass().getDeclaredMethod("getValue").invoke(constant);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }

                if (value == null) {
                    nameToConstant.put(name, constant);
                    constantToName.put(constant, name);
                } else {
                    nameToConstant.put(value.toString(), constant);
                    constantToName.put(constant, value);
                }
            }
        } catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
    }
    @Override public T read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
        }
        return nameToConstant.get(in.nextString());
    }

    @Override public void write(JsonWriter out, T value) throws IOException {
        if (null == value) {
            out.nullValue();
            return;
        }
        Object o = constantToName.get(value);
        if (o instanceof String) {
            out.value((String) o);
        } else if (o instanceof Integer) {
            out.value((Integer) o);
        } else if (o instanceof Boolean) {
            out.value((Boolean) o);
        } else if (o instanceof Double) {
            out.value((Double) o);
        } else if (o instanceof Long) {
            out.value((Long) o);
        } else if (o instanceof Number) {
            out.value((Number) o);
        } else {
            out.nullValue();
        }
    }

    public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() {
        @SuppressWarnings({"rawtypes", "unchecked"})
        @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            Class<? super T> rawType = typeToken.getRawType();
            if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
                return null;
            }
            if (!rawType.isEnum()) {
                rawType = rawType.getSuperclass(); // handle anonymous subclasses
            }
            return (TypeAdapter<T>) new EnumTypeAdapter(rawType);
        }
    };
}
